#!/usr/bin/python
# -*- coding: utf-8; tab-width: 4; indent-tabs-mode: nil; -*-

import pygame
from pygame.locals import *
import Elements

if not hasattr( pygame.transform, "smoothscale" ):
    print "install pygame 1.8 for better scaling!"
    pygame.transform.smoothscale = pygame.transform.scale

try:
    import psyco
    psyco.full()
except:
    print "install psyco for better performace!"

"""

Remember: NO MAGIC NUMBERS
Ricorda: NIENTE NUMERI MAGICI

"""

CLIPSIZE_X = 1024                   # dimensioni della finestra virtuale sulla gameboard (larghezza)
CLIPSIZE_Y = 768                    # dimensioni della finestra virtuale sulla gameboard (altezza)
FLAGS      = DOUBLEBUF|RESIZABLE    # flag per la finestra reale del gioco
FPS        = 50                     # velocità di aggiornamento _massima_ in frame per secondo
MARGIN     = 20                     # dimensione (in pixel) del margine dello schermo sensibile per lo zoom

MAX_ZOOM   = 1.5
ZOOM       = 1
MIN_ZOOM   = 0.1

X = 0
Y = 1

###############################################################################
# GAME AREA                                                                   #
###############################################################################

class GameArea( pygame.Surface ):
    def __init__( self, game ):
        self.screen = None
        self.size = (game.width, game.height)
        self.zoom = ZOOM
        self.scaled = self
        self.redraw = True

        pygame.Surface.__init__( self, self.size )

        self.sprites = pygame.sprite.OrderedUpdates()

        for element in game.materials:
            self.sprites.add( element )

        self.sprites.draw( self )

        self.clipsize = [ CLIPSIZE_X, CLIPSIZE_Y ] # dimensioni della 'finestra' sulla game area
        self.pan = [ self.size[X]/2, self.size[Y]/2 ]

        # Create a font
        self.font = pygame.font.Font(None, 18)


    def set_screen ( self, screen ):
        self.screen = screen
        self.redraw = True

    def set_pan ( self, pan ):
        self.screen.fill( (0,0,0) )
        # TODO: controllo limiti
        self.pan = pan
        self.redraw = True
        return self.pan

    def draw( self ):
        # le coordinate del pan sono negative perchè in realtà sono riferite
        # allo screen e non alla gamearea come nel resto di tutto il programma
        #
        # Stessa considerazione sulla modifica del pan di clipsize/2
        #
        if self.redraw:
            self.redraw = False
            self.screen.blit( self.scaled, (self.clipsize[X] / 2 - self.pan[X]*self.zoom , self.clipsize[Y] / 2 - self.pan[Y]*self.zoom) )
            self.screen.blit( self.hud, (0,0) )

            text = self.font.render('x:% 4d   y:% 4d   z % 3d' % (self.pan[X], self.pan[Y], int(self.zoom * 100)), True, (0,255, 0))
            textRect = text.get_rect()
            textRect.right = self.screen.get_rect().right
            self.screen.blit(text, textRect)

            return True
        else:
            return False

    def set_clipsize( self, size ):
        self.clipsize = size

        # test di sovraimpressione
        self.hud = pygame.Surface( self.clipsize )
        self.hud.set_colorkey( (0,0,0) )
        pygame.draw.aalines( self.hud, (0,255,0), True, [
            (MARGIN,                    MARGIN),
            (self.clipsize[X] - MARGIN, MARGIN),
            (self.clipsize[X] - MARGIN, self.clipsize[Y] - MARGIN),
            (MARGIN,                    self.clipsize[Y] - MARGIN)
            ] )

        self.redraw = True


    def set_zoom( self, zoom ):
        zoom = min( MAX_ZOOM, max(MIN_ZOOM, zoom ) )
        if zoom == 1:
            w = self.size[X]
            h = self.size[Y]
            self.scaled = self
        else:
            w = int( self.size[X] * zoom )
            h = int( self.size[Y] * zoom )
            self.scaled = pygame.transform.smoothscale( self, ( w, h ) )
        self.screen.fill( (0,0,0) )
        self.zoom = zoom
        self.redraw = True
        return self.zoom


###############################################################################
# TOUCH PANEL                                                                 #
###############################################################################

class TouchPanel( object ):
    def __init__( self, gamearea, size=(CLIPSIZE_X,CLIPSIZE_Y), flags=FLAGS, title="TouchMe(tm)", fps=FPS, margin=MARGIN ):
        self.fps = fps
        self.margin = margin
        self.flags = flags
        self.size = size
        self.handler = None
        self.gamearea = gamearea
        self.screen = pygame.display.set_mode( size, flags )
        pygame.display.set_caption( title )
        self.gamearea.set_screen( self.screen )
        self.gamearea.set_clipsize( size )

    def run( self ):
        running = True
        clock = pygame.time.Clock()

        while running:
            for event in pygame.event.get():
                if event.type == NOEVENT:
                    continue
                else:
                    running = self._process( event )

            if self.gamearea.draw():
                pygame.display.flip()

            clock.tick( self.fps )

    def _process( self, event ):
        if event.type == QUIT:
            return False

        elif event.type == KEYDOWN:
            if event.key == K_ESCAPE:
                return False

        elif event.type == KEYUP:
            pass

        elif event.type == MOUSEBUTTONDOWN:
            if event.button == 4: # wheel up
                print "Wheel UP"
            elif event.button == 5: # wheel down
                print "Wheel DOWN"
            else:
                # se il click e' entro i margini esterni dello screen parte l'azione di zoom
                pos = pygame.mouse.get_pos()
                if pos[X] < self.margin \
                or pos[X] > ( self.size[X] - self.margin ) \
                or pos[Y] < self.margin \
                or pos[Y] > ( self.size[Y] - self.margin ):
                    self.handler = Zoom( self, pos )
                else:
                    # controlliamo se ci sono sprite sotto il cursore (banale!)
                    # lo 0.5 serve per l'arrotondamento
                    cursor = ( int( 0.5 + self.gamearea.pan[X] - (self.size[X]/2 - pos[X]) / self.gamearea.zoom ),
                               int( 0.5 + self.gamearea.pan[Y] - (self.size[Y]/2 - pos[Y]) / self.gamearea.zoom ) )

                    self.handler = Move( self, pos )

                    for sprite in self.gamearea.sprites.sprites():
                        if sprite.is_draggable():
                            if sprite.get_hit( cursor ):
                                self.handler.add( sprite )

                    # se non ci sono sprite, iniziamo un pan di tutta la gamearea
                    # altrimenti muoviamo i token
                    if self.handler.is_empty():
                        self.handler = Pan( self, pos )
                    else:
                        self.handler.store() # registra la posizione iniziale di tutti gli sprite interessati

        elif event.type == MOUSEMOTION:
            if self.handler:
                self.handler.add_point( pygame.mouse.get_pos() )

        elif event.type == MOUSEBUTTONUP:
            if self.handler:
                self.handler.end()
                self.handler = None

        elif event.type == VIDEORESIZE:
            self.size = event.size
            self.screen = pygame.display.set_mode( event.size, self.flags )
            self.gamearea.set_clipsize( event.size )

        return True

    def register( self, type, handler ):
        pass

###############################################################################
# ZOOM                                                                        #
###############################################################################

class Zoom( object ):
    def __init__( self, touchpanel, start ):
        self.touchpanel = touchpanel
        self.start = start
        self.zoom = touchpanel.gamearea.zoom
        self.lastzoom = 0
        self.maxzoom = MAX_ZOOM
        self.minzoom = MIN_ZOOM

    def add_point( self, point ):
        # è l'ottusità dello zoom (più è alto meno lo zoom è sensibile al movimento)
        dullness = 800.0

        z = round( ( point[Y] - self.start[Y] ) / dullness, 2 )

        actualzoom = round( ( self.zoom + z) / 10, 2 ) * 10 # zoom step: 10%
        actualzoom = min( self.maxzoom, max(self.minzoom, actualzoom ) )

        if self.lastzoom != actualzoom:
            self.lastzoom = self.touchpanel.gamearea.set_zoom( actualzoom ) # Veeeeeeryactual!!!

            # aggiusta automaticamente il livello minimo e massimo di zoom
            # nel caso in cui la gamearea la pensi diversamente dallo zoom

            if self.lastzoom > actualzoom:
                self.minzoom = self.lastzoom
            if self.lastzoom < actualzoom:
                self.maxzoom = self.lastzoom

    def end( self ):
        pass

###############################################################################
# PAN                                                                         #
###############################################################################

class Pan( object ):
    def __init__( self, touchpanel, start ):
        self.touchpanel = touchpanel
        self.start = start
        self.pan = self.touchpanel.gamearea.pan

    def add_point( self, point ):
        deltax = round( ( self.start[X] - point[X] ) / self.touchpanel.gamearea.zoom )
        deltay = round( ( self.start[Y] - point[Y] ) / self.touchpanel.gamearea.zoom )
        self.touchpanel.gamearea.set_pan( ( self.pan[X] + deltax, self.pan[Y] + deltay ) )

    def end( self ):
        pass

###############################################################################
# MOVE                                                                        #
###############################################################################


"""
Spostare uno o più token:
    * registrare in un gruppo tutti i token da spostare (init)
    * creare un immagine dell'area visualizzata _senza_ i token da spostare (init)
    * cancellare i token spostati dall'area visualizzata, utilizzando l'immagine
      di cui sopra come immagine utilizzata per rimpiazzare i pixel cancellati (move)
    * disegnare i token spostati (move)
    * a termine del movimento, disegnare i token nella posizione definitiva (sia
      zoomati che nella versione 1:1)

IN TEORIA, dovrebbe bastare avere le immagini dei token già zoomate per evitare di dover
eseguire lo zoom tutte le volte che il mouse viene spostato
"""


class Move( pygame.sprite.OrderedUpdates ):
    def __init__( self, touchpanel, start ):
        pygame.sprite.OrderedUpdates.__init__( self )
        self.touchpanel = touchpanel
        self.start = start
        self.zoom = touchpanel.gamearea.zoom
        self.pan = touchpanel.gamearea.pan

    def add_point( self, point ):
        deltax = round( ( self.start[X] - point[X] ) / self.touchpanel.gamearea.zoom )
        deltay = round( ( self.start[Y] - point[Y] ) / self.touchpanel.gamearea.zoom )

        self.clear( self.touchpanel.gamearea.scaled, self.scaled )

        for sprite in self.sprites():
            sprite.move_offset( ( deltax, deltay ) )

        self.draw( self.touchpanel.gamearea.scaled )

        self.touchpanel.gamearea.set_pan( self.pan )

    def end( self ):
        self.scaled = None # fondamentale per la memoria (garbage collector buggato??)
        pass

    def store( self ):
        for sprite in self.sprites():
            sprite.store()

        # calcola l'immagine già zoomata _senza_ gli sprite selezionati
        surface = pygame.Surface( self.touchpanel.gamearea.size )

        group = pygame.sprite.OrderedUpdates()

        for sprite in self.touchpanel.gamearea.sprites.sprites():
            if sprite not in self.sprites():
                group.add( sprite )

        group.draw( surface )

        w = int( self.touchpanel.gamearea.size[X] * self.zoom )
        h = int( self.touchpanel.gamearea.size[Y] * self.zoom )

        self.scaled = pygame.transform.smoothscale( surface, ( w, h ) )
        self.touchpanel.gamearea.scaled.blit( self.scaled, (0,0) )
        self.touchpanel.gamearea.set_pan( self.pan )

    def is_empty( self ):
        return len( self.sprites() ) == 0

###############################################################################
#                                                                             #
# MAIN                                                                        #
#                                                                             #
###############################################################################

if __name__ == "__main__":
    import cProfile
    pygame.init()
    game = __import__( "dune" )
    gamearea = GameArea( game )
    tp = TouchPanel( gamearea=gamearea )
    cProfile.run('tp.run()')